package com.zmq.pic.common;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

/**
 * 扩展HibernateDaoSupport的泛型基类
 * 
 * @author 菠萝大象
 * @param <T>
 *            实体类型
 */
@Repository
@SuppressWarnings("unchecked")
public class HibernateDao<T> extends HibernateDaoSupport {

	protected Logger logger = LoggerFactory.getLogger(getClass());
	protected Class<T> entityClass;

	/**
	 * 在构造函数中利用反射机制获得参数T的具体类
	 */
	public HibernateDao() {
		entityClass = ReflectUtils.getClassGenricType(getClass());
	}

	/**
	 * 根据实体类与ID获得对象
	 * 
	 * @param clazz
	 *            实体类
	 * @param id
	 *            主键ID
	 */
	public <X> X get(final Class<X> clazz, final Serializable id) {
		return (X) getSession().get(clazz, id);
	}

	/**
	 * 根据实体类与ID获得对象
	 * 
	 * @param clazz
	 *            实体类
	 * @param id
	 *            主键ID
	 */
	public <X> X load(final Class<X> clazz, final Serializable id) {
		return (X) getSession().load(clazz, id);
	}

	/**
	 * @Title load
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 根据IDload方法查找Entity
	 * @param id
	 * @return T
	 * @throws
	 */
	public T load(Serializable id) {
		return load(entityClass, id);
	}

	/**
	 * 根据id获得对象
	 * 
	 * @param id
	 *            主键ID
	 */
	public T get(Serializable id) {
		return get(entityClass, id);
	}

	/**
	 * 删除对象
	 * 
	 * @param entity
	 *            实体类
	 */
	public void delete(final Object entity) {
		getSession().delete(entity);
	}

	/**
	 * 根据ID删除对象
	 * 
	 * @param id
	 *            主键ID
	 */
	public void delete(final Serializable id) {
		delete(get(id));
	}

	/**
	 * 根据实体类与ID删除对象
	 * 
	 * @param clazz
	 *            实体类
	 * @param id
	 *            主键ID
	 */
	public void delete(final Class clazz, final Serializable id) {
		delete(get(clazz, id));
	}

	/**
	 * 保存对象
	 * 
	 * @param entity
	 *            保存的实体对象
	 */
	public void save(final Object entity) {
		if (entity == null) {
			return;
		}
		getSession().saveOrUpdate(entity);
	}

	public void merge(final Object entity) {
		if (entity == null) {
			return;
		}

		getSession().merge(entity);
	}

	/**
	 * 获取所有数据
	 * 
	 * @param entityClass
	 *            参数T的反射类型
	 */
	public <X> List<X> getAll(final Class<X> entityClass) {
		return createCriteria(entityClass).list();
	}

	/**
	 * 获取所有数据
	 */
	public List<T> findAll() {
		return find();
	}

	/**
	 * 按属性查找对象列表,匹配方式为相等.
	 */
	public List<T> findBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return find(criterion);
	}

	/**
	 * 按属性查找唯一对象,匹配方式为相等.
	 */
	public T findByUnique(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return (T) createCriteria(criterion).uniqueResult();
	}

	/**
	 * @Title findById
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 根据Entity主键ID查找对象
	 * @param entity
	 * @return
	 * @return T
	 * @throws
	 */
	public T findById(T entity) {
		Serializable id = getId(entity);
		if (id != null) {
			return get(id);
		} else {
			return null;
		}
	}

	public T loadById(T entity) {
		Serializable id = getId(entity);
		if (id != null) {
			return load(id);
		} else {
			return null;
		}
	}

	/**
	 * @Title getId
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 获取Entity的主键ID
	 * @param entity
	 * @return Serializable
	 * @throws
	 */
	private Serializable getId(T entity) {
		try {
			Method method = entityClass.getMethod("getId");
			if (method != null) {
				Serializable value = (Serializable) method.invoke(entity);
				return value;
			}
		} catch (SecurityException e) {
			// e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// e.printStackTrace();
		} catch (IllegalAccessException e) {
			// e.printStackTrace();
		} catch (InvocationTargetException e) {
			// e.printStackTrace();
		}
		return null;
	}

	/**
	 * 根据条件获取数据
	 * 
	 * @param criterions
	 *            数量可变的Criterion
	 */
	public List<T> find(final Criterion... criterions) {
		return createCriteria(criterions).list();
	}

	/**
	 * HQL方式查询
	 * 
	 * @param hql
	 *            符合HQL语法的查询语句
	 * @param values
	 *            数量可变的条件值,按顺序绑定
	 */
	public Query createQuery(final String hql, final Object... values) {
		Query query = getSession().createQuery(hql);
		int j = values.length;
		for (int i = 0; i < j; i++)
			query.setParameter(i, values[i]);
		return query;
	}

	/**
	 * SQL方式查询
	 * 
	 * @param sql
	 *            符合SQL语法的查询语句
	 * @param values
	 *            数量可变的条件值,按顺序绑定
	 */
	public SQLQuery createSQLQuery(final String sql, final Object... values) {
		SQLQuery query = getSession().createSQLQuery(sql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}

	/**
	 * 根据类型创建查询对象
	 * 
	 * @param clazz
	 *            类型
	 */
	public Criteria createCriteria(final Class clazz) {
		return getSession().createCriteria(clazz);
	}

	/**
	 * 对象化查询
	 * 
	 * @param entityClass
	 *            参数T的反射类型
	 * @param criterions
	 *            数量可变的Criterion
	 */
	public Criteria createCriteria(final Class clazz,
			final Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(clazz);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 对象化查询
	 * 
	 * @param criterions
	 *            数量可变的Criterion
	 */
	public Criteria createCriteria(final Criterion... criterions) {
		return createCriteria(entityClass, criterions);
	}

	/**
	 * 保存新增或修改的对象.
	 * 
	 * @param entity
	 */
	public void saveOrUpdate(final T entity) {
		Assert.notNull(entity, "entity不能为空");
		getSession().saveOrUpdate(entity);
		logger.debug("saveOrUpdate entity: {}", entity);
	}

	/**
	 * @Title saveAll
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 批处理提交
	 * @param collection
	 * @return void
	 * @throws
	 */
	public void saveAll(final Collection<T> collection) {

		Assert.notNull(collection, "entity不能为空");
		for (T entity : collection) {
			save(entity);
			logger.debug("save entity: {}", entity);
		}
	}

	/**
	 * 更新对象
	 * 
	 * @param entity
	 */
	public void update(final T entity) {
		Assert.notNull(entity, "entity不能为空");
		if (entity == null) {
			return;
		}
		getSession().update(entity);
	}

	/**
	 * @Title findByExample
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 根据entity查找跟entity属性相同的实体
	 * @param entity
	 * @return
	 * @return List<T>
	 * @throws
	 */
	public List<T> findByExample(final T entity) {
		return getHibernateTemplate().findByExample(entity);
	}

	/**
	 * @Title find
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 根据HQL，map进行分页查询,查询结果保存到page中
	 * @param hql
	 * @param values
	 * @param page
	 * @return void
	 * @throws
	 */
	public void find(final String hql, final Map<String, Object> values,
			final Page<T> page) {
		Assert.notNull(page, "page不能为空");
		Query query = createQuery(hql, values);
		if (page.isAutoCount()) {
			long totalSize = countHqlResult(hql, values);
			page.setTotalSize(totalSize);
		}
		setPageParameter(query, page);
		List<T> results = query.list();
		page.setResults(results);
	}

	/**
	 * @Title countHqlResult
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 执行count查询获得本次Hql查询所能获得的对象总数. </br>
	 *              本函数只能自动处理简单的hql语句,复杂的hql查询请另行编写count语句查询.
	 * @param hql
	 * @param values
	 * @return
	 * @return Long
	 * @throws
	 */
	protected Long countHqlResult(final String hql,
			final Map<String, Object> values) {
		long count = 0;
		String fromHql = hql;
		// select子句与order by子句会影响count查询,进行简单的排除.
		fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
		fromHql = StringUtils.substringBefore(fromHql, "order by");
		String countHql = "select count(*) " + fromHql;

		try {
			count = findLong(countHql, values);
		} catch (Exception e) {
			throw new RuntimeException("hql can't be auto count, hql is:"
					+ countHql, e);
		}

		return count;
	}

	/**
	 * @Title findLong
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 按HQL查询Long类型结果.
	 * @param hql
	 * @param values
	 * @return
	 * @return Long
	 * @throws
	 */
	public Long findLong(final String hql, final Map<String, Object> values) {
		return (Long) findUnique(hql, values);
	}

	/**
	 * @Title findUnique
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 按HQL查询唯一对象.
	 * @param hql
	 * @param values
	 * @return
	 * @return T
	 * @throws
	 */
	public T findUnique(final String hql, final Map<String, Object> values) {
		return (T) createQuery(hql, values).uniqueResult();
	}

	/**
	 * @Title createQuery
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 根据查询HQL与参数列表创建Query对象.
	 * @param queryString
	 * @param values
	 * @return
	 * @return Query
	 * @throws
	 */
	private Query createQuery(final String queryString,
			final Map<String, Object> values) {
		Assert.hasText(queryString, "queryString不能为空");
		Query query = getSession().createQuery(queryString);
		if (values != null) {
			query.setProperties(values);
		}
		return query;
	}

	/**
	 * @Title setPageParameter (Oracle)
	 * @author 陈希峰 chenxf10@126.com
	 * @Description 设置分页参数到Query对象,辅助函数.
	 * @param q
	 * @param page
	 * @return Query
	 * @throws
	 */
	private Query setPageParameter(final Query query, final Page<T> page) {
		query.setFirstResult(page.getFirst() - 1);
		query.setMaxResults(page.getSizeOfPerPage());
		return query;
	}

}